#include "DbStorage/RequirementsDbStorage.hpp"
#include "DbStorage/DbStorageFactory.hpp"
#include "Storage/PackageStorage.hpp"
#include "Model/Project.hpp"
#include "Model/Package.hpp"
#include "Model/Requirement.hpp"
#include "CaseDB/RecordSet.hpp"
#include "CaseDB/Connection.hpp"
#include "CaseDB/Statement.hpp"
#include <boost/foreach.hpp>
#include <boost/any.hpp>
#include <algorithm>
#include <list>
#include <cassert>

namespace DbStorage
{


RequirementsDbStorage::RequirementsDbStorage( Storage::StorageFactory& factory, CaseDB::Connection* pConnection )
	: Storage::RequirementsStorage( factory)
	, DbStorageBase( pConnection)
{

}

// ---------------------------------------------------------------------------------------------------------------------

void RequirementsDbStorage::loadRequirements( Model::Project& prjToLoad )
{
	using namespace CaseDB;

	assert( !prjToLoad.storedID().empty());

	RecordSet::Metadata rsMeta;
	rsMeta.reserve( 4);
	rsMeta.push_back( DbTypesEnum::Integer);	// FK_PARENT_PACKAGE
	rsMeta.push_back( DbTypesEnum::Integer);	// PK_REQUIREMENT
	rsMeta.push_back( DbTypesEnum::Integer);	// FK_PARENT_REQUIREMENT
	rsMeta.push_back( DbTypesEnum::String);		// NAME
	rsMeta.push_back( DbTypesEnum::String);		// STATEMENT
	rsMeta.push_back( DbTypesEnum::Integer);	// POSITION
	RecordSet results( rsMeta);

	// It is very important the ORDER BY clause of this select. This optimizes the raw data processing.
	Statement rqQuery( 
		"select R.FK_PACKAGE, R.PK_REQUIREMENT, R.FK_PARENT_REQUIREMENT, R.NAME, R.STATEMENT, R.POSITION "
		"from REQUIREMENTS R inner join PACKAGES P on R.FK_PACKAGE = P.PK_PACKAGE "
		"where P.FK_PROJECT = ? "
		"order by R.FK_PACKAGE, R.POSITION, R.FK_PARENT_REQUIREMENT");
	rqQuery.addParameter( DbTypesEnum::Integer, prjToLoad.storedID());
	dynamic_cast< DbStorageFactory&>( rStorageFactory_).dbConnection().execute( rqQuery, results);

	RequirementRawData loadedData;
	this->makeRawData( results, loadedData);
	this->processRawData( prjToLoad, loadedData);
}

// ---------------------------------------------------------------------------------------------------------------------

void RequirementsDbStorage::makeRawData( const CaseDB::RecordSet& results, RequirementRawData& rawData )
{
	using boost::any_cast;
	using namespace CaseDB;
	RecordSet::ConstRecordIterator recI = results.begin(), recEndI = results.end();
	while( recI != recEndI)
	{
		const RecordSet::Record& curRec = *recI;

		int fkParentPackage = any_cast<int>( curRec[0]);
		int pkRequirement = any_cast<int>( curRec[1]);
		int fkParentRequirement = curRec[2].empty()? -1 : any_cast<int>( curRec[2]);
		std::string name = any_cast< std::string>( curRec[3]);
		std::string statement = any_cast< std::string>( curRec[4]);
		int position = any_cast< int>( curRec[5]);
		rawData.insert( std::make_pair( fkParentRequirement,
				// [PK_REQUIREMENT, FK_PACKAGE, POSITION, NAME, STATEMENT]
			boost::make_tuple( pkRequirement, fkParentPackage, position, name, statement)));

		++recI;
	}
}

// ---------------------------------------------------------------------------------------------------------------------

void RequirementsDbStorage::processRawData( Model::Project& prjToLoad, const RequirementRawData& rawData )
{
	using namespace Model;
	using namespace boost;

	// First, find the root requirements.
	std::list< Requirement*> rootRequirements;
	RequirementRawData::const_iterator rootI = rawData.find( -1),
		endI = rawData.end();
	while( (rootI != endI) && (-1 == rootI->first))
	{
		int parentPackageID = rootI->second.get<1>();
		Package* pParentPackage = rStorageFactory_.getPackageStorage().deepSearchByStoredID( 
			&prjToLoad.rootPackage(), parentPackageID);
		assert( NULL != pParentPackage);
		assert( rootI->second.get<1>() == any_cast< int>( pParentPackage->storedID()));
		std::auto_ptr< Requirement> pNewRequirement( new Requirement( rootI->second.get<3>(), rootI->second.get<4>()));
		pNewRequirement->setStoredID( rootI->second.get<0>());
		rootRequirements.push_back( pNewRequirement.get());
		pParentPackage->addRequirement( pNewRequirement.release());
		++rootI;
	}

	// Continue processing of children
	BOOST_FOREACH( Requirement* pCurRq, rootRequirements)
	{
		this->processChildRequirement( pCurRq, rawData);
	}
}

// ---------------------------------------------------------------------------------------------------------------------

void RequirementsDbStorage::processChildRequirement( Model::Requirement* pCurRq, const RequirementRawData& rawData )
{
	using namespace Model;
	using namespace boost;

	// It assumes the raw data is ordered by position for the same parent

	int parentId = any_cast< int>(pCurRq->storedID());
	RequirementRawData::const_iterator rootI = rawData.find( parentId),
		endI = rawData.end();
	int lastPos = 0;
	while( (rootI != endI) && (parentId == rootI->first))
	{
		++lastPos;
		// Sanity check: the positions of the children are the right ones (also ordered).
		assert( lastPos == rootI->second.get<2>());
		std::auto_ptr< Requirement> pChidRq( new Requirement( rootI->second.get<3>(), rootI->second.get<4>()));
		pChidRq->setStoredID( rootI->second.get<0>());
		Requirement *pRq = pCurRq->addChildRequirement( pChidRq.release(), lastPos);
		// Sanity check: the parent package is the right one.
		assert( rootI->second.get<1>() == any_cast< int>( pRq->package()->storedID()));

		++rootI;
	}

	// Now, process grand children:
	Requirement::ChildIterator 
		childI = pCurRq->childrenBegin()
		, endChildI = pCurRq->childrenEnd();
	while( childI != endChildI)
	{
		this->processChildRequirement( *childI, rawData);

		++childI;
	}
}

} // namespace DbStorage
